Purpose &
Project Metadata

This project explores the Medicaid Drug Rebate Program (MDRP) database. Data is retrieved via the MDRP API (description here) as well as general URL file retrieval routines:

  1. Data Dictionary (Medicaid.gov)
  2. MDRP Data
  3. openFDA Drug Data (used to augment MDRP data)
  4. Route of Administration FDA.gov

All files for this project are hosted on GitHub


Required LIbraries

CRAN GitHub
purrr
jsonlite
httr
summarytools
munsell
cachem
SmartEDA
htmltools
slider
stringi
magrittr
plotly
DT
data.table
pdftools
lubridate
future
furrr
book.of.utilities
book.of.features
book.of.workflow
architect
smart.data
event.vectors

Data
Wrangling

Retrieve and Prepare Data

The data were retrieved via R package httr with some initial conversion to data.table objects. Core objects were cached to disk (cachem) for easy retrieval after the initial pull.

Data Sets

MDRP Data

if (!"api_data" %in% .cache$keys()){ 
  download_temp <- tempfile()
  
  as.character(urls$data) |> 
    stri_extract_all_regex("http.+csv", simplify = TRUE) |> 
    as.vector() |>
    download.file(destfile = download_temp) 
    
  .tmp_obj <- read.csv(download_temp) |> as.data.table(na.rm = FALSE) 
}

if (!"api_data" %in% ls()){ 
  makeActiveBinding("api_data", function(){ .cache$get("api_data") }, env = globalenv())
}

Formatting updates include the following:

  • Convert field ‘NDC’ and fields ending in ‘Code’ to characters (numeral-encoded nominal values)
  • Converting date fields to date format
  • Replace ‘.’ in field names with ’ ’

Dictionary

if (!"api_dictionary" %in% .cache$keys()){ 
  .cache$set("api_dictionary", invisible( 
    as.character(urls$data) |> 
    stri_extract_all_regex("http.+pdf", simplify = TRUE) |> 
    as.vector() |>
    GET() |>
    content() |> 
    pdf_text()))
}  

.summary_labels <- invisible({
  .pattern <- c("Pkg"
                , "Intro"
                , "COD Status"
                , "FDA Application Number"
                , "FDA Therapeutic Equivalence Code"
                );
  .replacement <- c("Package"
                , "Intro.+Date"
                , "Covered Outpatient Drug [(]COD[)] Status"
                , "FDA Application Number/OTC Monograph Number"
                , "TEC"
                );
  
  names(api_data) |>
    rlang::set_names() |>
    imap_chr(\(x, y){
      .out <- .cache$get("api_dictionary") |> 
        stri_extract_all_regex(
          sprintf(
            fmt = "(%s)[:]\n.+"
            , stri_replace_all_fixed(str = x, pattern = .pattern , replacement = .replacement, vectorize_all = FALSE)
            )
        , simplify = TRUE
        ) |>
        stats::na.omit() |>
        as.vector() |> paste(collapse = "\n")
      
      if (rlang::is_empty(.out)){ 
        y 
      } else{ 
        .out <- paste(.out, collapse = "\n")
        ifelse(stringi::stri_length(.out) > 50, paste0(stri_sub(.out, length = 50), " ..."), .out)
      }
    })
})

.tmp_obj <- api_data;

iwalk(.summary_labels, \(x, y){ 
  .tmp_obj <<- modify_at(.tmp_obj, y, \(i){ attr(i, "label") <- x; i }) 
})

.cache$set("api_data", .tmp_obj)

OpenFDA Data

if (!"open_fda_ndc" %in% .cache$keys()){
  json.file <- paste0(params$data_dir, "/drug-ndc-0001-of-0001.json");
  download.file <- tempfile();
  
  if (!file.exists(json.file)){ 
    tags$p(sprintf("Retrieve data from '%s'", urls$openFDA)) |> print()
    
    GET(urls$data$children |> stri_extract_first_regex("https.+json.zip"),
      write_disk(path = download.file, overwrite = TRUE));
    
    unzip(zipfile = download.file, exdir = "data")
  }
      
  .cache$set("open_fda_ndc", { read_json(path = json.file) %$% {
    map(results, as.data.table) |> rbindlist(fill = TRUE) |>  
      setattr("metadata", meta)}
    })
}

if (!"openFDA_ndc" %in% ls()){ 
    makeActiveBinding("openFDA_ndc", function(){ .cache$get("open_fda_ndc")}, env = environment())
  }

NDC Format Inspection

NDC sequences come in a various formats, usually a 4-4-x, 5-4-x, or 5-3-x sequence (each integer indicating string length). Sometimes other formats arise, so normalizing all NDC sequences is a good idea, especially when there is a desire (or need) to join different data containing intersecting NDCs.

The following shows proportional representation of NDC formats in the OpenFDA and MDRP data, respectively:

The MDRP has many more NDC sequences due to truncation of leading zeroes. Fortunately, an NDC sequence is a collection of code segments (present in the data) concatenated with a hyphen. Knowing this, A function (check_ndc_format() — see setup.R was created in order to derive conformed NDC segment sequences (using labeler and product codes) based on the OpenFDA sequences, allowing the MDRP and OpenFDA data to be joined later in the process.

Master Drug
Data

The OpenFDA and MDRP data were joined using the conformed NDC sequence in the previous subsection to create master_drug_data (Note: due to the size of the data, the join operation takes some time which is why the result is disk-cached for later retrieval. This caching approach is used for data retrieved or created during the data wrangling phase.):

if (params$refresh || !"master_drug_data" %in% .cache$keys()){
  .cache$set("master_drug_data", (\(x, i){
    x[i
      , on = "alt_ndc==product_ndc"
      , allow.cartesian = TRUE
      , `:=`(pharm_class = pharm_class
             , dea_schedule = dea_schedule
             , product_type = product_type
             , route = route
             , marketing_category = marketing_category
             )
      , by = .EACHI
      ][
      , `:=`(
        pharm_class = map_chr(pharm_class, \(x) unlist(x) %||% "~")
        , route = map_chr(route, \(x) unlist(x) %||% "~")
        )
      ]
    })(
    api_data %>% 
      setnames(stri_replace_all_fixed(names(.) |> tolower(), " ", "_")) %>% 
      .[, alt_ndc := map2_chr(labeler_code, product_code, check_ndc_format)]
    , openFDA_ndc
    ))
}
if (!"master_drug_data" %in% ls()){ 
  makeActiveBinding("master_drug_data", function(){ .cache$get("master_drug_data") }, env = globalenv());
}

Event Metrics

master_drug_data is a great data set for constructing simple, time-based metrics. Given the natural order of the types of events, it is easy to setup event sequence metrics using package lubridate. The metrics to be created are described below:

Metric Name Description
days_to_market Days between approval and market release
on_market_age Days active on market
days_market_absent Days most-recently absent from market

Drug Events
Creation

My next task was to add the date-differential metrics mentioned in the previous subsection. As an intermediate object, I created ndc_events by looking at what appears to the be natural chronology of dates: fda_approval_date -> market_date -> termination_date -> reactivation_date.

Some of the values in columns termination_date and reactivation_date are NA indicating the event did not happen. This would obviously need to be addressed in deriving the metrics logic, and after several rounds of trial-and-error, I worked out such logic, discovering the following in the process:

  • The metrics are hierarchically-contingent based on whether or not NA values exist and if so, which of termination_date, reactivation_date, or both
  • Some values in termination_date and reactivation_date are future-dated relative to “today”: these were converted to NA before deriving the metrics as they haven’t happened yet (NA \(\equiv\) Didn’t happen (yet))
  • A small subset of observations having the FDA approval date after the listed market date

The resulting object was captured in ndc_events_clean:

ndc_events_clean <- { 
  define(
    ndc_events
    , modify_at(.SD, c("termination_date", "reactivation_date"), \(x) ifelse(today() < x, NA, x))
    , cbind(
        .SD
        , define({
            .SD[, fda_approval_date:reactivation_date][, map(.SD, as.numeric)] |> 
              # dplyr::slice_sample(prop = 0.4) |>
              apply(1, \(x){
                c(x, diff(x) |> modify_if(is.na, \(i) 0) |> sign() %>% .[-1]) |> 
                  as.list() |>
                  modify_at(c(5, 6), \(i) i == 1) %>% 
                  rlang::set_names(names(.)[c(1:4)], paste0(names(.)[c(5, 6)], ".bool"))
                }, simplify = FALSE) |>
              rbindlist()
            }
          , days_to_market = market_date - fda_approval_date
          , on_market_age = 
              apply(.SD[, .(termination_date.bool, reactivation_date.bool, termination_date)]
                    , 1, function(i){ ifelse(i[[1]], ifelse(i[[2]], today(), i[[3]]), today()) }) -
              apply(.SD[, .(termination_date.bool, reactivation_date.bool, market_date, reactivation_date)]
                    , 1, function(i){ ifelse(i[[1]], ifelse(i[[2]], i[[4]], i[[3]]), i[[3]]) })
          , days_market_absent = 
              apply(.SD[, .(reactivation_date.bool, reactivation_date)]
                    , 1, function(i){ ifelse(i[[1]], i[[2]], today()) }) -
                apply(.SD[, .(termination_date.bool, termination_date)]
                    , 1, function(i){ ifelse(i[[1]], i[[2]], today()) })
          , ~days_to_market + on_market_age + days_market_absent
          )
      )
    , modify_at(.SD, c("termination_date", "reactivation_date"), \(x) as.Date(x, origin = "1970-01-01"))
  )}

#
(\(x, i, by){
  i <- define(x[i, on = by, allow.cartesian = TRUE]) ;
  imap(.ndc_events_meta, \(x, y){
    rlang::inject(descr(x = modify_at(i, y, \(j) as.numeric(j, units = "days")), var = !!rlang::sym(y), transpose = !TRUE)) |> 
      view(method = "render", table.classes = 'multi_stat', custom.css = "markdown.css") |>
      tags$td()
  })
})(master_drug_data, ndc_events_clean, c("alt_ndc", "fda_application_number", "market_date", "termination_date", "reactivation_date", "fda_approval_date")) |>
tags$tr() |>
tags$table()

Descriptive Statistics

days_to_market

N: 1418180
days_to_
market
Mean 785.99
Std.Dev 1849.98
Min -9859.00
Q1 0.00
Median 59.00
Q3 378.00
Max 11870.00
MAD 87.47
IQR 378.00
CV 2.35
Skewness 3.00
SE.Skewness 0.00
Kurtosis 9.41
N.Valid 1418180
Pct.Valid 100.00

Generated by summarytools 1.0.1 (R version 4.1.3)
2023-05-30

Descriptive Statistics

on_market_age

N: 1418180
on_market_
age
Mean 5300.18
Std.Dev 3320.28
Min 1.00
Q1 2751.00
Median 4400.00
Q3 7314.00
Max 11930.00
MAD 3034.88
IQR 4563.00
CV 0.63
Skewness 0.71
SE.Skewness 0.00
Kurtosis -0.58
N.Valid 1418180
Pct.Valid 100.00

Generated by summarytools 1.0.1 (R version 4.1.3)
2023-05-30

Descriptive Statistics

days_market_absent

N: 1418180
days_market_
absent
Mean 160.09
Std.Dev 637.07
Min -3002.00
Q1 0.00
Median 0.00
Q3 0.00
Max 11107.00
MAD 0.00
IQR 0.00
CV 3.98
Skewness 4.87
SE.Skewness 0.00
Kurtosis 30.38
N.Valid 1418180
Pct.Valid 100.00

Generated by summarytools 1.0.1 (R version 4.1.3)
2023-05-30

Some of the ‘Max’/‘Min’ values are negative; however, the number of records is relatively small and, more importantly, explainable:

  • days_to_market: Approval occurred after the market date
  • days_market_absent: Records where the termination date was non-NA but after the market date

Drug Events
Visualization

Combining the master drug data and event data (master_drug_data + ndc_events_clean), after some trial-and-error, I settled on the following showing the root-mean-square of metric values grouped by route of administration:

Question I
Correlation

I decided to start asking some questions of the data given the metrics defined earlier. I decided to look into the correlation between pairs of metrics relative to a third:

Market Age

“How does the correlation between days_to_market and on_market_age change by route of administration?”:

suppressWarnings(suppressMessages({
library(smart.data)

if (!"smrt_drugs" %in% .cache$keys()){
  smrt.drug_obs_data <- smart.data$
    new(x = .cache$get("drug_obs_data") |> print(), name = "drugs")$
    taxonomy.rule(
      term.map = if ("smrt_drug_taxonomy" %in% .cache$keys()){ .cache$get("smrt_drug_taxonomy") } else { data.table(term = "metrics", desc = "Metrics related to events and other descriptive statistics") }
      , update = !("smrt_drug_taxonomy" %in% .cache$keys())
      )$
    cache_mgr(action = upd) |>
    invisible();
  
  .cache$set("smrt_drugs", smrt.drug_obs_data);
  .cache$set("smrt_drug_taxonomy", smrt.drug_obs_data$smart.rules$for_usage)
} else { 
  invisible(smart.data$new(as.data.table(x = 1), "none"))
  .cache$get("smrt_drugs")$cache_mgr(action = upd) 
}

get.smart("drugs")$use(identifier, metrics, retain = c(route), subset = days_to_market >= 0) |> 
  # View() 
  setkey(route, alt_ndc, days_to_market, on_market_age) |>
  split_f(~route) |>
  map_dbl(\(x) x %$% cor(as.numeric(days_to_market), as.numeric(on_market_age))) |>
  modify_if(is.na, \(x) 0) |> 
  (\(x){ 
    x <- x[order(x)]
    nm <- names(x)
    z <- calc.zero_mean(x, as.zscore = TRUE, use.population = TRUE)
    y <- ratio(x + abs(min(x)), type="pareto", decimals = 6)
    
    .wh_scale <- 800 * c(1.2, .7)
    
    plot_ly(
      x = z
      , y = y
      , size = 5 * exp(x) + 10
      , width = .wh_scale[1]
      , height = .wh_scale[2]
      , hoverinfo = "text"
      , hovertext = sprintf(fmt ="<b>%s</b><br><b>Y:</b> %.2f%% of Total<br><b>Cor</b>(days_to_market, on_market_age): %.2f<br><b>Z-score</b>(X): %.2f", nm, y * 100, x, z)
      , color = x
      , stroke = I("black")
      , type = "scatter", mode = "markers"
      ) |>
      config(mathjax = "cdn", displayModeBar = FALSE) |>
      layout(
        xaxis = list(
            title = list(
              text = "Z-score<sub>X</sub>: X | Cor(m<sub>0</sub>, m<sub>1</sub>) ~ Route"
              , font = list(size = 16, family = "Georgia"))
            , gridcolor = "#FFFFFF"
            )
        , yaxis = list(
            title = list(
              text = "Cumulative Proportion (X)"
              , font = list(size = 16, family = "Georgia"))
            , gridcolor = "#FFFFFF"
            )
        , title = list(
            text = sprintf("Correlation Coefficient (<span style='text-emphasis-position:under; text-emphais: filled red double-circle; '>%s</span> vs. <span style='text-emphasis-position:under; text-emphais: filled red double-circle; '>%s</span>) by Route of Administration", "days_to_market", "on_market_age")
            , font = list(family = "Georgia"))
        , plot_bgcolor = rgb(.8,.8,.8)
        , margin = list(b = 30, t = 50)
        ) 
  })()
}))
Warning: `line.width` does not currently support multiple values.Warning: `line.width` does not currently support multiple values.

Days Absent from Market

“How does the correlation between days_to_market and days_market_absent change by route of administration?”:

get.smart("drugs")$use(identifier, metrics, retain = c(route), subset = days_to_market >= 0) |> 
  # View() 
  setkey(route, alt_ndc, days_market_absent, on_market_age) |>
  split_f(~route) |>
  map_dbl(\(x) x %$% suppressWarnings(cor(as.numeric(days_market_absent), as.numeric(on_market_age)))) |>
  modify_if(is.na, \(x) 0) |> 
  (\(x){ 
    x <- x[order(x)]
    nm <- names(x)
    z <- calc.zero_mean(x, as.zscore = TRUE, use.population = TRUE)
    y <- ratio(x + abs(min(x)), type="pareto", decimals = 6)
    
    .wh_scale <- 800 * c(1.2, .7)
    
    plot_ly(
      x = z
      , y = y
      , size = 5 * exp(x) + 10
      , width = .wh_scale[1]
      , height = .wh_scale[2]
      , hoverinfo = "text"
      , hovertext = sprintf(fmt ="<b>%s</b><br><b>Y:</b> %.2f%% of Total<br><b>Cor</b>(days_to_market, days_market_absent): %.2f<br><b>Z-score</b>(X): %.2f", nm, y * 100, x, z)
      , color = x
      , stroke = I("black")
      , type = "scatter"
      , mode = "markers"
      ) |>
      config(mathjax = "cdn", displayModeBar = FALSE) |>
      layout(
        xaxis = list(
            title = list(
              text = "Z-score<sub>X</sub>: X | Cor(m<sub>0</sub>, m<sub>1</sub>) ~ Route"
              , font = list(size = 16, family = "Georgia"))
            , gridcolor = "#FFFFFF"
            )
        , yaxis = list(
            title = list(
              text = "Cumulative Proportion (X)"
              , font = list(size = 16, family = "Georgia"))
            , gridcolor = "#FFFFFF"
            )
        , title = list(
            text = sprintf("Correlation Coefficient (<span style='text-emphasis-position:under; text-emphais: filled red double-circle; '>%s</span> vs. <span style='text-emphasis-position:under; text-emphais: filled red double-circle; '>%s</span>) by Route of Administration", "days_to_market", "days_market_absent")
            , font = list(family = "Georgia"))
        , plot_bgcolor = rgb(.8,.8,.8)
        , margin = list(b = 30, t = 50)
        ) 
})()
Warning: `line.width` does not currently support multiple values.Warning: `line.width` does not currently support multiple values.
Market age relative to days to market shows more variability in correlation distribution. This is not to make an claim of statistically significant differentiation; however, it may be worth exploring whether or not there are clusters of administration routes based on event correlation. A future update may address this, but this is as deep of exploration I’ll go for now.

Question II
Event Clusters

Another way to look at the data is to consider grouped durations and the cross-temporal sequences among them. As this forms the basis of a network, I’ll use custom package event.vectors to accomplish this in a future update.

LS0tDQp0aXRsZTogIkV4cGxvcmluZyB0aGUgTWVkaWNhaWQgRHJ1ZyBSZWJhdGUgUHJvZ3JhbSAoTURSUCkgRGF0YWJhc2UiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY3NzOiBtYXJrZG93bi5jc3MNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBjc3M6IG1hcmtkb3duLmNzcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KcGFyYW1zOg0KICBkYXRhX2RpcjogZGF0YQ0KICBjcmFuX2xpYnM6ICFyIGMoJ3B1cnJyJywgJ2pzb25saXRlJywgJ2h0dHInLCAnc3VtbWFyeXRvb2xzJywgJ211bnNlbGwnLCAnY2FjaGVtJywgJ1NtYXJ0RURBJywgJ2h0bWx0b29scycsICdzbGlkZXInLCAnc3RyaW5naScsICdtYWdyaXR0cicsICdwbG90bHknLCAnRFQnLCAnZGF0YS50YWJsZScsICdwZGZ0b29scycsICdsdWJyaWRhdGUnLCAnZnV0dXJlJywgJ2Z1cnJyJywgJ2Z1dHVyZS5jYWxscicpDQogIGdpdF9saWJzOiAhciBwYXN0ZTAoJ2Jvb2sub2YuJywgYygndXRpbGl0aWVzJywgJ2ZlYXR1cmVzJywgJ3dvcmtmbG93JykpIHw+IGMoJ2FyY2hpdGVjdCcsICdzbWFydC5kYXRhJywgJ2V2ZW50LnZlY3RvcnMnKQ0KICByZWZyZXNoOiAhciBGQUxTRQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBvcHRzX2NodW5rID0gbGlzdChjYWNoZT1UUlVFLCBjYWNoZS5sYXp5PVRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UpDQogICkNCg0Kc291cmNlKCJzZXR1cC5SIiwgbG9jYWw9VFJVRSkNCmBgYA0KDQpgYGB7PWpzfQ0KPHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIGFzeW5jDQogIHNyYz0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvbWF0aGpheC8yLjcuNy9NYXRoSmF4LmpzP2NvbmZpZz1UZVgtTU1MLUFNX0NIVE1MIj4NCjwvc2NyaXB0Pg0KYGBgDQojICB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KIyMgUHVycG9zZSAmPGJyPiBQcm9qZWN0IE1ldGFkYXRhDQoNClRoaXMgcHJvamVjdCBleHBsb3JlcyB0aGUgTWVkaWNhaWQgRHJ1ZyBSZWJhdGUgUHJvZ3JhbSAoTURSUCkgZGF0YWJhc2UuICBEYXRhIGlzIHJldHJpZXZlZCB2aWEgdGhlIE1EUlAgW0FQSV0oaHR0cHM6Ly9kYXRhLm1lZGljYWlkLmdvdi9kYXRhc2V0LzBhZDY1ZmU1LTNhZDMtNWQ3OS1hM2Y5LTc4OTNkZWQ3OTYzYSkgKGRlc2NyaXB0aW9uIFtoZXJlXShodHRwczovL3d3dy5tZWRpY2FpZC5nb3YvbWVkaWNhaWQvcHJlc2NyaXB0aW9uLWRydWdzL21lZGljYWlkLWRydWctcmViYXRlLXByb2dyYW0vbWVkaWNhaWQtZHJ1Zy1yZWJhdGUtcHJvZ3JhbS1kYXRhL2luZGV4Lmh0bWwpKSBhcyB3ZWxsIGFzIGdlbmVyYWwgVVJMIGZpbGUgcmV0cmlldmFsIHJvdXRpbmVzOg0KDQpgYGB7ciBVUkxzLCBlY2hvPUZBTFNFfQ0KdXJscyRkYXRhDQpgYGANCg0KQWxsIGZpbGVzIGZvciB0aGlzIHByb2plY3QgYXJlIGhvc3RlZCBvbiBbR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vZGVscmlhYW4vcG9ydF9NRFJQX2RhdGFiYXNlKQ0KDQo8aHI+DQoNCiMjIyBSZXF1aXJlZCBMSWJyYXJpZXMNCg0KYGBge3IgZWNobz1GQUxTRX0NCnRhZ3MkdGFibGUoDQogIGlkID0gInJlcV9saWJzX3RhYmxlIg0KICAsIHRhZ3MkdHIoDQogICAgICB0YWdzJHRoKGNsYXNzPSJyZXFfbGlicyBjcmFuX2xpYnMiLCAiQ1JBTiIsIGNvbHNwYW4gPSAzLCB3aWR0aD0iNjclOyAiKQ0KICAgICAgLCB0YWdzJHRoKGNsYXNzPSJyZXFfbGlicyBnaXRfbGlicyIsICJHaXRIdWIiLCB3aWR0aCA9ICIqIikNCiAgICAgICkNCiAgLCB0YWdzJHRyKA0KICAgICAgc3R5bGUgPSAiYWxpZ246dG9wIg0KICAgICAgLCBzbGlkZXI6OnNsaWRlKA0KICAgICAgICAgIHBhcmFtcyRjcmFuX2xpYnMNCiAgICAgICAgICAsIC5hZnRlciA9IDUNCiAgICAgICAgICAsIC5zdGVwID0gNg0KICAgICAgICAgICwgLmYgPSBcKHgpeyANCiAgICAgICAgICAgICAgICBtYXAoeCwgXChpKSB0YWdzJHNwYW4oaSwgdGFncyRicigpKSkgfD4gDQogICAgICAgICAgICAgICAgdGFncyR0ZChjbGFzcz0icmVxX2xpYnMgZ2l0X2xpYnMiKQ0KICAgICAgICAgICAgICB9DQogICAgICAgICAgLCAuY29tcGxldGUgPSBUUlVFDQogICAgICAgICAgKSB8PiANCiAgICAgICAgICBwdXJycjo6Y29tcGFjdCgpDQogICAgICAsIHRhZ3MkdGQoY2xhc3M9InJlcV9saWJzIGNyYW5fbGlicyIsIHVybHMkZ2l0X2xpYnMpDQogICAgICApDQogICkgfD4gdGFncyRwKCkNCmBgYA0KDQoNCiMjIERhdGEgPGJyPldyYW5nbGluZw0KDQojIyMgUmV0cmlldmUgYW5kIFByZXBhcmUgRGF0YSB7LnRhYnNldH0NCg0KVGhlIGRhdGEgd2VyZSByZXRyaWV2ZWQgdmlhIFIgcGFja2FnZSBbaHR0cl0oaHR0cHM6Ly9odHRyLnItbGliLm9yZykgd2l0aCBzb21lIGluaXRpYWwgY29udmVyc2lvbiB0byBbZGF0YS50YWJsZV0oaHR0cHM6Ly9yZGF0YXRhYmxlLmdpdGxhYi5pby9kYXRhLnRhYmxlLykgb2JqZWN0cy4gIENvcmUgb2JqZWN0cyB3ZXJlIGNhY2hlZCB0byBkaXNrIChbY2FjaGVtXShodHRwczovL2NhY2hlbS5yLWxpYi5vcmcpKSBmb3IgZWFzeSByZXRyaWV2YWwgYWZ0ZXIgdGhlIGluaXRpYWwgcHVsbC4NCg0KIyMjIyBEYXRhIFNldHMNCg0KKipNRFJQIERhdGEqKg0KDQpgYGB7ciBSRVRSSUVWRV9EQVRBX01EUlAsIGNvbGxhcHNlPUZBTFNFfSANCmlmICghImFwaV9kYXRhIiAlaW4lIC5jYWNoZSRrZXlzKCkpeyANCiAgZG93bmxvYWRfdGVtcCA8LSB0ZW1wZmlsZSgpDQogIA0KICBhcy5jaGFyYWN0ZXIodXJscyRkYXRhKSB8PiANCiAgICBzdHJpX2V4dHJhY3RfYWxsX3JlZ2V4KCJodHRwLitjc3YiLCBzaW1wbGlmeSA9IFRSVUUpIHw+IA0KICAgIGFzLnZlY3RvcigpIHw+DQogICAgZG93bmxvYWQuZmlsZShkZXN0ZmlsZSA9IGRvd25sb2FkX3RlbXApIA0KICAgIA0KICAudG1wX29iaiA8LSByZWFkLmNzdihkb3dubG9hZF90ZW1wKSB8PiBhcy5kYXRhLnRhYmxlKG5hLnJtID0gRkFMU0UpIA0KfQ0KDQppZiAoISJhcGlfZGF0YSIgJWluJSBscygpKXsgDQogIG1ha2VBY3RpdmVCaW5kaW5nKCJhcGlfZGF0YSIsIGZ1bmN0aW9uKCl7IC5jYWNoZSRnZXQoImFwaV9kYXRhIikgfSwgZW52ID0gZ2xvYmFsZW52KCkpDQp9DQoNCmBgYA0KDQpGb3JtYXR0aW5nIHVwZGF0ZXMgaW5jbHVkZSB0aGUgZm9sbG93aW5nOg0KDQotICAgQ29udmVydCBmaWVsZCAnTkRDJyBhbmQgZmllbGRzIGVuZGluZyBpbiAnQ29kZScgdG8gY2hhcmFjdGVycyAobnVtZXJhbC1lbmNvZGVkIG5vbWluYWwgdmFsdWVzKQ0KLSAgIENvbnZlcnRpbmcgZGF0ZSBmaWVsZHMgdG8gZGF0ZSBmb3JtYXQNCi0gICBSZXBsYWNlICcuJyBpbiBmaWVsZCBuYW1lcyB3aXRoICcgJw0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KaWYgKCEiYXBpX2RhdGEiICVpbiUgLmNhY2hlJGtleXMoKSl7DQogIHN1cHByZXNzV2FybmluZ3MoLnRtcF9vYmogJT4lDQogICAgbW9kaWZ5X2F0KGxzKC4sIHBhdHRlcm4gPSAiKE5EQ3xDb2RlKSQiKSwgYXMuY2hhcmFjdGVyKSAlPiUNCiAgICBtb2RpZnlfYXQobHMoLiwgcGF0dGVybiA9ICJEYXRlIiksIGx1YnJpZGF0ZTo6bWR5KSAlPiUgDQogICAgc2V0bmFtZXMoc3RyaV9yZXBsYWNlX2FsbF9maXhlZChuYW1lcyguKSwgIi4iLCAiICIpKSB8Pg0KICAgIChcKHgpIC5jYWNoZSRzZXQoa2V5ID0gImFwaV9kYXRhIiwgdmFsdWUgPSB4KSkoKSkNCn0NCmBgYA0KDQoqKkRpY3Rpb25hcnkqKg0KDQpgYGB7ciBSRVRSSUVWRV9EQVRBX0RJQ1RJT05BUll9DQppZiAoISJhcGlfZGljdGlvbmFyeSIgJWluJSAuY2FjaGUka2V5cygpKXsgDQogIC5jYWNoZSRzZXQoImFwaV9kaWN0aW9uYXJ5IiwgaW52aXNpYmxlKCANCiAgICBhcy5jaGFyYWN0ZXIodXJscyRkYXRhKSB8PiANCiAgICBzdHJpX2V4dHJhY3RfYWxsX3JlZ2V4KCJodHRwLitwZGYiLCBzaW1wbGlmeSA9IFRSVUUpIHw+IA0KICAgIGFzLnZlY3RvcigpIHw+DQogICAgR0VUKCkgfD4NCiAgICBjb250ZW50KCkgfD4gDQogICAgcGRmX3RleHQoKSkpDQp9ICANCg0KLnN1bW1hcnlfbGFiZWxzIDwtIGludmlzaWJsZSh7DQogIC5wYXR0ZXJuIDwtIGMoIlBrZyINCiAgICAgICAgICAgICAgICAsICJJbnRybyINCiAgICAgICAgICAgICAgICAsICJDT0QgU3RhdHVzIg0KICAgICAgICAgICAgICAgICwgIkZEQSBBcHBsaWNhdGlvbiBOdW1iZXIiDQogICAgICAgICAgICAgICAgLCAiRkRBIFRoZXJhcGV1dGljIEVxdWl2YWxlbmNlIENvZGUiDQogICAgICAgICAgICAgICAgKTsNCiAgLnJlcGxhY2VtZW50IDwtIGMoIlBhY2thZ2UiDQogICAgICAgICAgICAgICAgLCAiSW50cm8uK0RhdGUiDQogICAgICAgICAgICAgICAgLCAiQ292ZXJlZCBPdXRwYXRpZW50IERydWcgWyhdQ09EWyldIFN0YXR1cyINCiAgICAgICAgICAgICAgICAsICJGREEgQXBwbGljYXRpb24gTnVtYmVyL09UQyBNb25vZ3JhcGggTnVtYmVyIg0KICAgICAgICAgICAgICAgICwgIlRFQyINCiAgICAgICAgICAgICAgICApOw0KICANCiAgbmFtZXMoYXBpX2RhdGEpIHw+DQogICAgcmxhbmc6OnNldF9uYW1lcygpIHw+DQogICAgaW1hcF9jaHIoXCh4LCB5KXsNCiAgICAgIC5vdXQgPC0gLmNhY2hlJGdldCgiYXBpX2RpY3Rpb25hcnkiKSB8PiANCiAgICAgICAgc3RyaV9leHRyYWN0X2FsbF9yZWdleCgNCiAgICAgICAgICBzcHJpbnRmKA0KICAgICAgICAgICAgZm10ID0gIiglcylbOl1cbi4rIg0KICAgICAgICAgICAgLCBzdHJpX3JlcGxhY2VfYWxsX2ZpeGVkKHN0ciA9IHgsIHBhdHRlcm4gPSAucGF0dGVybiAsIHJlcGxhY2VtZW50ID0gLnJlcGxhY2VtZW50LCB2ZWN0b3JpemVfYWxsID0gRkFMU0UpIA0KICAgICAgICAgICAgKQ0KICAgICAgICAsIHNpbXBsaWZ5ID0gVFJVRQ0KICAgICAgICApIHw+DQogICAgICAgIHN0YXRzOjpuYS5vbWl0KCkgfD4NCiAgICAgICAgYXMudmVjdG9yKCkgfD4gcGFzdGUoY29sbGFwc2UgPSAiXG4iKQ0KICAgICAgDQogICAgICBpZiAocmxhbmc6OmlzX2VtcHR5KC5vdXQpKXsgDQogICAgICAgIHkgDQogICAgICB9IGVsc2V7IA0KICAgICAgICAub3V0IDwtIHBhc3RlKC5vdXQsIGNvbGxhcHNlID0gIlxuIikNCiAgICAgICAgaWZlbHNlKHN0cmluZ2k6OnN0cmlfbGVuZ3RoKC5vdXQpID4gNTAsIHBhc3RlMChzdHJpX3N1Yigub3V0LCBsZW5ndGggPSA1MCksICIgLi4uIiksIC5vdXQpDQogICAgICB9DQogICAgfSkNCn0pDQoNCi50bXBfb2JqIDwtIGFwaV9kYXRhOw0KDQppd2Fsayguc3VtbWFyeV9sYWJlbHMsIFwoeCwgeSl7IA0KICAudG1wX29iaiA8PC0gbW9kaWZ5X2F0KC50bXBfb2JqLCB5LCBcKGkpeyBhdHRyKGksICJsYWJlbCIpIDwtIHg7IGkgfSkgDQp9KQ0KDQouY2FjaGUkc2V0KCJhcGlfZGF0YSIsIC50bXBfb2JqKQ0KYGBgDQoNCioqT3BlbkZEQSBEYXRhKioNCg0KYGBge3IgUkVUUklFVkVfREFUQV9PUEVORkRBLCB3YXJuaW5nPUZBTFNFfQ0KaWYgKCEib3Blbl9mZGFfbmRjIiAlaW4lIC5jYWNoZSRrZXlzKCkpew0KICBqc29uLmZpbGUgPC0gcGFzdGUwKHBhcmFtcyRkYXRhX2RpciwgIi9kcnVnLW5kYy0wMDAxLW9mLTAwMDEuanNvbiIpOw0KICBkb3dubG9hZC5maWxlIDwtIHRlbXBmaWxlKCk7DQogIA0KICBpZiAoIWZpbGUuZXhpc3RzKGpzb24uZmlsZSkpeyANCiAgICB0YWdzJHAoc3ByaW50ZigiUmV0cmlldmUgZGF0YSBmcm9tICclcyciLCB1cmxzJG9wZW5GREEpKSB8PiBwcmludCgpDQogICAgDQogICAgR0VUKHVybHMkZGF0YSRjaGlsZHJlbiB8PiBzdHJpX2V4dHJhY3RfZmlyc3RfcmVnZXgoImh0dHBzLitqc29uLnppcCIpLA0KICAgICAgd3JpdGVfZGlzayhwYXRoID0gZG93bmxvYWQuZmlsZSwgb3ZlcndyaXRlID0gVFJVRSkpOw0KICAgIA0KICAgIHVuemlwKHppcGZpbGUgPSBkb3dubG9hZC5maWxlLCBleGRpciA9ICJkYXRhIikNCiAgfQ0KICAgICAgDQogIC5jYWNoZSRzZXQoIm9wZW5fZmRhX25kYyIsIHsgcmVhZF9qc29uKHBhdGggPSBqc29uLmZpbGUpICUkJSB7DQogICAgbWFwKHJlc3VsdHMsIGFzLmRhdGEudGFibGUpIHw+IHJiaW5kbGlzdChmaWxsID0gVFJVRSkgfD4gIA0KICAgICAgc2V0YXR0cigibWV0YWRhdGEiLCBtZXRhKX0NCiAgICB9KQ0KfQ0KDQppZiAoISJvcGVuRkRBX25kYyIgJWluJSBscygpKXsgDQogICAgbWFrZUFjdGl2ZUJpbmRpbmcoIm9wZW5GREFfbmRjIiwgZnVuY3Rpb24oKXsgLmNhY2hlJGdldCgib3Blbl9mZGFfbmRjIil9LCBlbnYgPSBlbnZpcm9ubWVudCgpKQ0KICB9DQoNCmBgYA0KDQojIyMjIE5EQyBGb3JtYXQgSW5zcGVjdGlvbiB7LnRhYnNldH0NCg0KTkRDIHNlcXVlbmNlcyBjb21lIGluIGEgdmFyaW91cyBmb3JtYXRzLCB1c3VhbGx5IGEgYDQtNC14YCwgYDUtNC14YCwgb3IgYDUtMy14YCBzZXF1ZW5jZSAoZWFjaCBpbnRlZ2VyIGluZGljYXRpbmcgc3RyaW5nIGxlbmd0aCkuIFNvbWV0aW1lcyBvdGhlciBmb3JtYXRzIGFyaXNlLCBzbyBub3JtYWxpemluZyBhbGwgTkRDIHNlcXVlbmNlcyBpcyBhIGdvb2QgaWRlYSwgZXNwZWNpYWxseSB3aGVuIHRoZXJlIGlzIGEgZGVzaXJlIChvciBuZWVkKSB0byBqb2luIGRpZmZlcmVudCBkYXRhIGNvbnRhaW5pbmcgaW50ZXJzZWN0aW5nIE5EQ3MuDQoNClRoZSBmb2xsb3dpbmcgc2hvd3MgcHJvcG9ydGlvbmFsIHJlcHJlc2VudGF0aW9uIG9mIE5EQyBmb3JtYXRzIGluIHRoZSAqT3BlbkZEQSogYW5kICpNRFJQKiBkYXRhLCByZXNwZWN0aXZlbHk6DQoNCmBgYHtyIE5EQ19GT1JNQVRTLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KbGlzdCgNCiAgYE9wZW5GREE6IE5EQyBGb3JtYXRzYCA9IG9wZW5GREFfbmRjWywgdW5pcXVlKHByb2R1Y3RfbmRjKV0gfD4gDQogICAgICBzb3J0KCkgfD4gDQogICAgICBzdHJpX3NwbGl0X2ZpeGVkKCItIikgfD4gDQogICAgICBtYXBfY2hyKFwoeCkgc3RyaV9sZW5ndGgoeCkgfD4gcGFzdGUoY29sbGFwc2UgPSAiLSIpKSB8PiANCiAgICAgIGZyZWR1Y2UobGlzdChzb3J0LCB0YWJsZSwgYXMuZGF0YS50YWJsZSwgXCh4KSBzZXRuYW1lcyh4LCBjKCJOREMuRm9ybWF0IiwgIk4iKSkpKSANCiAgLCBgTURSUDogTkRDIEZvcm1hdHNgID0gKGFwaV9kYXRhICU+JSANCiAgICAgICAgc2V0bmFtZXMoc3RyaV9yZXBsYWNlX2FsbF9maXhlZChuYW1lcyguKSB8PiB0b2xvd2VyKCksICIgIiwgIl8iKSkNCiAgICAgICAgKVssIHBhc3RlKHN0cmlfbGVuZ3RoKGxhYmVsZXJfY29kZSksIHN0cmlfbGVuZ3RoKHByb2R1Y3RfY29kZSksIHNlcCA9ICItIildIHw+DQogICAgICAgIGZyZWR1Y2UobGlzdChzb3J0LCB0YWJsZSwgYXMuZGF0YS50YWJsZSwgXCh4KSBzZXRuYW1lcyh4LCBjKCJOREMuRm9ybWF0IiwgIk4iKSkgfD4gc2V0a2V5KE4pKSkgfD4NCiAgICAgICAgZGVmaW5lKA0KICAgICAgICAgIE5EQy5Gb3JtYXQgPSBpZmVsc2UocmF0aW8oTiwgdHlwZSA9ICJwYXJldG8iLCBkZWNpbWFscyA9IDYpIDwgMC4xLCAiT3RoZXI8YnI+PHN1cD4lcyBmb3JtYXRzPC9zdXA+IiwgTkRDLkZvcm1hdCkgJT4lDQogICAgICAgICAgICBtb2RpZnlfYXQoDQogICAgICAgICAgICAgIC5hdCA9IHdoaWNoKGdyZXBsKCJPdGhlciIsIC4pKQ0KICAgICAgICAgICAgICAsIC5mID0gXCh4KSBzcHJpbnRmKHgsIHN1bShncmVwbCgiT3RoZXIiLCAuKSkpDQogICAgICAgICAgICAgICkNCiAgICAgICAgICAsIE4gPSBzdW0oTikgfiBOREMuRm9ybWF0DQogICAgICAgICAgKQ0KICApIHw+IA0KaW1hcChcKHgsIHkpew0KICBwbG90X2x5KCAgDQogICAgZGF0YSA9IHgNCiAgICAsIHR5cGUgPSAicGllIg0KICAgICwgbGFiZWxzID0gfk5EQy5Gb3JtYXQNCiAgICAsIHZhbHVlcyA9IH5ODQogICAgLCBob2xlID0gMC42DQogICAgLCB3aWR0aCA9IDUwMA0KICAgICwgaGVpZ2h0ID0gNDUwDQogICAgLCByb3RhdGlvbiA9IGlmZWxzZShncmVwbCgiTURSUCIsIHkpLCAzMywgMCkNCiAgICAsIG5hbWUgPSBOVUxMDQogICAgLCB0ZXh0aW5mbz0nbGFiZWwrcGVyY2VudCcNCiAgICAsIGluc2lkZXRleHRvcmllbnRhdGlvbj0ncmFkaWFsJw0KICAgICkgfD4NCiAgICBhZGRfdGV4dCh4ID0gMC41LCB5ID0gMC41DQogICAgICAgICAgICAgLCB4cmVmID0gInBhcGVyIiwgeXJlZiA9ICJwYXBlciINCiAgICAgICAgICAgICAsIHRleHQgPSBpZmVsc2UoZ3JlcGwoIk1EUlAiLCB5KSwgIk1EUlAiLCAiT3BlbkZEQSIpDQogICAgICAgICAgICAgLCBmb250ID0gbGlzdChmYW1pbHkgPSAiR2VvcmdpYSIsIHNpemUgPSAyMikpIHw+DQogICAgcGxvdGx5OjpsYXlvdXQoDQogICAgICB4YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSkNCiAgICAgICwgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpDQogICAgICApIHw+DQogICAgcGxvdGx5Ojpjb25maWcoZGlzcGxheU1vZGVCYXI9IEZBTFNFKSB8Pg0KICAgIHRhZ3MkdGQoKQ0KfSkgfD4NCnRhZ3MkdHIoKSB8Pg0KdGFncyR0YWJsZSgpDQpgYGANCg0KVGhlIE1EUlAgaGFzIG1hbnkgbW9yZSBOREMgc2VxdWVuY2VzIGR1ZSB0byB0cnVuY2F0aW9uIG9mIGxlYWRpbmcgemVyb2VzLiBGb3J0dW5hdGVseSwgYW4gTkRDIHNlcXVlbmNlIGlzIGEgY29sbGVjdGlvbiBvZiBjb2RlIHNlZ21lbnRzIChwcmVzZW50IGluIHRoZSBkYXRhKSBjb25jYXRlbmF0ZWQgd2l0aCBhIGh5cGhlbi4gS25vd2luZyB0aGlzLCBBIGZ1bmN0aW9uIChgY2hlY2tfbmRjX2Zvcm1hdCgpYCAmbWRhc2g7IHNlZSBgciBodG1sdG9vbHM6OnRhZ3MkYShocmVmID0gIi5cXHNldHVwLlIiLCB0YXJnZXQ9Il9ibGFuayIsICJzZXR1cC5SIilgIHdhcyBjcmVhdGVkIGluIG9yZGVyIHRvIGRlcml2ZSBjb25mb3JtZWQgTkRDIHNlZ21lbnQgc2VxdWVuY2VzICh1c2luZyBsYWJlbGVyIGFuZCBwcm9kdWN0IGNvZGVzKSBiYXNlZCBvbiB0aGUgT3BlbkZEQSBzZXF1ZW5jZXMsIGFsbG93aW5nIHRoZSBNRFJQIGFuZCBPcGVuRkRBIGRhdGEgdG8gYmUgam9pbmVkIGxhdGVyIGluIHRoZSBwcm9jZXNzLg0KDQoNCiMjIE1hc3RlciBEcnVnPGJyPkRhdGEgey50YWJzZXQgLnRhYmV0LWZhZGV9DQoNClRoZSBPcGVuRkRBIGFuZCBNRFJQIGRhdGEgd2VyZSBqb2luZWQgdXNpbmcgdGhlIGNvbmZvcm1lZCBOREMgc2VxdWVuY2UgaW4gdGhlIHByZXZpb3VzIHN1YnNlY3Rpb24gdG8gY3JlYXRlIGBtYXN0ZXJfZHJ1Z19kYXRhYCAoKipOb3RlKio6IGR1ZSB0byB0aGUgc2l6ZSBvZiB0aGUgZGF0YSwgdGhlIGpvaW4gb3BlcmF0aW9uIHRha2VzIHNvbWUgdGltZSB3aGljaCBpcyB3aHkgdGhlIHJlc3VsdCBpcyBkaXNrLWNhY2hlZCBmb3IgbGF0ZXIgcmV0cmlldmFsLiBUaGlzIGNhY2hpbmcgYXBwcm9hY2ggaXMgdXNlZCBmb3IgZGF0YSByZXRyaWV2ZWQgb3IgY3JlYXRlZCBkdXJpbmcgdGhlIGRhdGEgd3JhbmdsaW5nIHBoYXNlLik6DQoNCmBgYHtyIE1BU1RFUl9EUlVHX0RBVEF9DQppZiAocGFyYW1zJHJlZnJlc2ggfHwgISJtYXN0ZXJfZHJ1Z19kYXRhIiAlaW4lIC5jYWNoZSRrZXlzKCkpew0KICAuY2FjaGUkc2V0KCJtYXN0ZXJfZHJ1Z19kYXRhIiwgKFwoeCwgaSl7DQogICAgeFtpDQogICAgICAsIG9uID0gImFsdF9uZGM9PXByb2R1Y3RfbmRjIg0KICAgICAgLCBhbGxvdy5jYXJ0ZXNpYW4gPSBUUlVFDQogICAgICAsIGA6PWAocGhhcm1fY2xhc3MgPSBwaGFybV9jbGFzcw0KICAgICAgICAgICAgICwgZGVhX3NjaGVkdWxlID0gZGVhX3NjaGVkdWxlDQogICAgICAgICAgICAgLCBwcm9kdWN0X3R5cGUgPSBwcm9kdWN0X3R5cGUNCiAgICAgICAgICAgICAsIHJvdXRlID0gcm91dGUNCiAgICAgICAgICAgICAsIG1hcmtldGluZ19jYXRlZ29yeSA9IG1hcmtldGluZ19jYXRlZ29yeQ0KICAgICAgICAgICAgICkNCiAgICAgICwgYnkgPSAuRUFDSEkNCiAgICAgIF1bDQogICAgICAsIGA6PWAoDQogICAgICAgIHBoYXJtX2NsYXNzID0gbWFwX2NocihwaGFybV9jbGFzcywgXCh4KSB1bmxpc3QoeCkgJXx8JSAifiIpDQogICAgICAgICwgcm91dGUgPSBtYXBfY2hyKHJvdXRlLCBcKHgpIHVubGlzdCh4KSAlfHwlICJ+IikNCiAgICAgICAgKQ0KICAgICAgXQ0KICAgIH0pKA0KICAgIGFwaV9kYXRhICU+JSANCiAgICAgIHNldG5hbWVzKHN0cmlfcmVwbGFjZV9hbGxfZml4ZWQobmFtZXMoLikgfD4gdG9sb3dlcigpLCAiICIsICJfIikpICU+JSANCiAgICAgIC5bLCBhbHRfbmRjIDo9IG1hcDJfY2hyKGxhYmVsZXJfY29kZSwgcHJvZHVjdF9jb2RlLCBjaGVja19uZGNfZm9ybWF0KV0NCiAgICAsIG9wZW5GREFfbmRjDQogICAgKSkNCn0NCmlmICghIm1hc3Rlcl9kcnVnX2RhdGEiICVpbiUgbHMoKSl7IA0KICBtYWtlQWN0aXZlQmluZGluZygibWFzdGVyX2RydWdfZGF0YSIsIGZ1bmN0aW9uKCl7IC5jYWNoZSRnZXQoIm1hc3Rlcl9kcnVnX2RhdGEiKSB9LCBlbnYgPSBnbG9iYWxlbnYoKSk7DQp9DQpgYGANCg0KIyMjIEV2ZW50IE1ldHJpY3MNCg0KYG1hc3Rlcl9kcnVnX2RhdGFgIGlzIGEgZ3JlYXQgZGF0YSBzZXQgZm9yIGNvbnN0cnVjdGluZyBzaW1wbGUsIHRpbWUtYmFzZWQgbWV0cmljcy4gR2l2ZW4gdGhlIG5hdHVyYWwgb3JkZXIgb2YgdGhlIHR5cGVzIG9mIGV2ZW50cywgaXQgaXMgZWFzeSB0byBzZXR1cCBldmVudCBzZXF1ZW5jZSBtZXRyaWNzIHVzaW5nIHBhY2thZ2UgW2BsdWJyaWRhdGVgXShodHRwczovL3Jkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9sdWJyaWRhdGUvdmVyc2lvbnMvMS45LjIpLiBUaGUgbWV0cmljcyB0byBiZSBjcmVhdGVkIGFyZSBkZXNjcmliZWQgYmVsb3c6DQoNCmBgYHtyIE5EQ19FVkVOVFNfTUVUUklDU19NRVRBLCBlY2hvPUZBTFNFfQ0KLm5kY19ldmVudHNfbWV0YSA8LSBybGFuZzo6c2V0X25hbWVzKA0KICAgIGMoIkRheXMgYmV0d2VlbiBhcHByb3ZhbCBcbmFuZCBtYXJrZXQgcmVsZWFzZSINCiAgICAgICAgLCAiRGF5cyBhY3RpdmUgb24gbWFya2V0Ig0KICAgICAgICAsICJEYXlzIG1vc3QtcmVjZW50bHkgXG5hYnNlbnQgZnJvbSBtYXJrZXQiKQ0KICAgICwgYygiZGF5c190b19tYXJrZXQiLCAib25fbWFya2V0X2FnZSIsICJkYXlzX21hcmtldF9hYnNlbnQiKQ0KICAgICk7DQoNCmFwcGVuZChsaXN0KGBNZXRyaWMgTmFtZWAgPSAiRGVzY3JpcHRpb24iKSwgLm5kY19ldmVudHNfbWV0YSkgfD4gDQogIGltYXAoXCh4LCB5KSANCiAgICBpZiAoZ3JlcGwoIk1ldHJpYyIsIHkpKXsgDQogICAgICB0YWdzJHRyKA0KICAgICAgICB0YWdzJHRoKHN0eWxlID0gImJhY2tncm91bmQtY29sb3I6ICNFRUVFRUU7IHBhZGRpbmc6MnB4OyAiLCB5KQ0KICAgICAgICAsIHRhZ3MkdGgoc3R5bGUgPSAiYmFja2dyb3VuZC1jb2xvcjogI0VFRUVFRTsgcGFkZGluZy1sZWZ0OiA1cHg7ICIsIHgpDQogICAgICAgICkNCiAgICAgIH0gZWxzZSB7IA0KICAgICAgdGFncyR0cigNCiAgICAgICAgdGFncyR0ZChzdHlsZSA9ICJmb250LXdlaWdodDpib2xkOyBwYWRkaW5nOjJweDsgdGV4dC1hbGlnbjpyaWdodDsgIiwgeSkNCiAgICAgICAgLCB0YWdzJHRkKHN0eWxlID0gInBhZGRpbmctbGVmdDogNXB4OyAiLCB4KQ0KICAgICAgICApDQogICAgICB9DQogICAgKSB8PiANCiAgdGFncyR0YWJsZSgpIHw+DQogIHRhZ3MkcCgpDQpgYGANCg0KIyMjIERydWcgRXZlbnRzPGJyPkNyZWF0aW9uDQoNCmBgYHtyIERSVUdfRVZFTlRTX1BSRVAsIGVjaG89RkFMU0V9DQppZiAocGFyYW1zJHJlZnJlc2ggfHwgISJuZGNfZXZlbnRzIiAlaW4lIC5jYWNoZSRrZXlzKCkpeyANCiAgLnRtcF9vYmogPC0gZGVmaW5lKA0KICAgICAgICBtYXN0ZXJfZHJ1Z19kYXRhDQogICAgICAgICwgfmFsdF9uZGMgKyBmZGFfYXBwbGljYXRpb25fbnVtYmVyICsgZmRhX2FwcHJvdmFsX2RhdGUgKyANCiAgICAgICAgICAgIG1hcmtldF9kYXRlICsgdGVybWluYXRpb25fZGF0ZSArIHJlYWN0aXZhdGlvbl9kYXRlDQogICAgICAgICwgdW5pcXVlKC5TRCkNCiAgICAgICAgKTsNCiAgDQogIGl3YWxrKC5uZGNfZXZlbnRzX21ldGEsIFwoeCwgeSl7IA0KICAgIC50bXBfb2JqIDw8LSBtb2RpZnlfYXQoLnRtcF9vYmosIHksIFwoaSl7IHNldGF0dHIoaSwgImxhYmVsIiwgeCkgfSkgDQogIH0pDQoNCiAgLmNhY2hlJHNldCgibmRjX2V2ZW50cyIsIC50bXBfb2JqKQ0KfQ0KDQppZiAoISJuZGNfZXZlbnRzIiAlaW4lIGxzKCkpeyANCiAgbWFrZUFjdGl2ZUJpbmRpbmcoIm5kY19ldmVudHMiLCBmdW5jdGlvbigpeyAuY2FjaGUkZ2V0KCJuZGNfZXZlbnRzIikgfSwgZW52ID0gZ2xvYmFsZW52KCkpIA0KfQ0KYGBgDQoNCk15IG5leHQgdGFzayB3YXMgdG8gYWRkIHRoZSBkYXRlLWRpZmZlcmVudGlhbCBtZXRyaWNzIG1lbnRpb25lZCBpbiB0aGUgcHJldmlvdXMgc3Vic2VjdGlvbi4gIEFzIGFuIGludGVybWVkaWF0ZSBvYmplY3QsIEkgY3JlYXRlZCBgbmRjX2V2ZW50c2AgYnkgbG9va2luZyBhdCB3aGF0IGFwcGVhcnMgdG8gdGhlIGJlIG5hdHVyYWwgY2hyb25vbG9neSBvZiBkYXRlczogYGZkYV9hcHByb3ZhbF9kYXRlYCAtPiBgbWFya2V0X2RhdGVgIC0+IGB0ZXJtaW5hdGlvbl9kYXRlYCAtPiBgcmVhY3RpdmF0aW9uX2RhdGVgLiAgDQoNClNvbWUgb2YgdGhlIHZhbHVlcyBpbiBjb2x1bW5zIGB0ZXJtaW5hdGlvbl9kYXRlYCBhbmQgYHJlYWN0aXZhdGlvbl9kYXRlYCBhcmUgYE5BYCBpbmRpY2F0aW5nIHRoZSBldmVudCBkaWQgbm90IGhhcHBlbi4gVGhpcyB3b3VsZCBvYnZpb3VzbHkgbmVlZCB0byBiZSBhZGRyZXNzZWQgaW4gZGVyaXZpbmcgdGhlIG1ldHJpY3MgbG9naWMsIGFuZCBhZnRlciBzZXZlcmFsIHJvdW5kcyBvZiB0cmlhbC1hbmQtZXJyb3IsIEkgd29ya2VkIG91dCBzdWNoIGxvZ2ljLCBkaXNjb3ZlcmluZyB0aGUgZm9sbG93aW5nIGluIHRoZSBwcm9jZXNzOg0KDQotIFRoZSBtZXRyaWNzIGFyZSBoaWVyYXJjaGljYWxseS1jb250aW5nZW50IGJhc2VkIG9uIHdoZXRoZXIgb3Igbm90IGBOQWAgdmFsdWVzIGV4aXN0IGFuZCBpZiBzbywgd2hpY2ggb2YgYHRlcm1pbmF0aW9uX2RhdGVgLCBgcmVhY3RpdmF0aW9uX2RhdGVgLCBvciBib3RoDQotIFNvbWUgdmFsdWVzIGluIGB0ZXJtaW5hdGlvbl9kYXRlYCBhbmQgYHJlYWN0aXZhdGlvbl9kYXRlYCBhcmUgZnV0dXJlLWRhdGVkIHJlbGF0aXZlIHRvICJ0b2RheSI6IHRoZXNlIHdlcmUgY29udmVydGVkIHRvIGBOQWAgYmVmb3JlIGRlcml2aW5nIHRoZSBtZXRyaWNzIGFzIHRoZXkgaGF2ZW4ndCBoYXBwZW5lZCB5ZXQgKGBOQWAgJFxlcXVpdiQgRGlkbid0IGhhcHBlbiAoX3lldF8pKQ0KLSBBIHNtYWxsIHN1YnNldCBvZiBvYnNlcnZhdGlvbnMgaGF2aW5nIHRoZSBGREEgYXBwcm92YWwgZGF0ZSAqKmFmdGVyKiogdGhlIGxpc3RlZCBtYXJrZXQgZGF0ZQ0KDQpUaGUgcmVzdWx0aW5nIG9iamVjdCB3YXMgY2FwdHVyZWQgaW4gYG5kY19ldmVudHNfY2xlYW5gOg0KDQpgYGB7ciBEUlVHX0VWRU5UU19NQUtFfQ0KbmRjX2V2ZW50c19jbGVhbiA8LSB7IA0KICBkZWZpbmUoDQogICAgbmRjX2V2ZW50cw0KICAgICwgbW9kaWZ5X2F0KC5TRCwgYygidGVybWluYXRpb25fZGF0ZSIsICJyZWFjdGl2YXRpb25fZGF0ZSIpLCBcKHgpIGlmZWxzZSh0b2RheSgpIDwgeCwgTkEsIHgpKQ0KICAgICwgY2JpbmQoDQogICAgICAgIC5TRA0KICAgICAgICAsIGRlZmluZSh7DQogICAgICAgICAgICAuU0RbLCBmZGFfYXBwcm92YWxfZGF0ZTpyZWFjdGl2YXRpb25fZGF0ZV1bLCBtYXAoLlNELCBhcy5udW1lcmljKV0gfD4gDQogICAgICAgICAgICAgICMgZHBseXI6OnNsaWNlX3NhbXBsZShwcm9wID0gMC40KSB8Pg0KICAgICAgICAgICAgICBhcHBseSgxLCBcKHgpew0KICAgICAgICAgICAgICAgIGMoeCwgZGlmZih4KSB8PiBtb2RpZnlfaWYoaXMubmEsIFwoaSkgMCkgfD4gc2lnbigpICU+JSAuWy0xXSkgfD4gDQogICAgICAgICAgICAgICAgICBhcy5saXN0KCkgfD4NCiAgICAgICAgICAgICAgICAgIG1vZGlmeV9hdChjKDUsIDYpLCBcKGkpIGkgPT0gMSkgJT4lIA0KICAgICAgICAgICAgICAgICAgcmxhbmc6OnNldF9uYW1lcyhuYW1lcyguKVtjKDE6NCldLCBwYXN0ZTAobmFtZXMoLilbYyg1LCA2KV0sICIuYm9vbCIpKQ0KICAgICAgICAgICAgICAgIH0sIHNpbXBsaWZ5ID0gRkFMU0UpIHw+DQogICAgICAgICAgICAgIHJiaW5kbGlzdCgpDQogICAgICAgICAgICB9DQogICAgICAgICAgLCBkYXlzX3RvX21hcmtldCA9IG1hcmtldF9kYXRlIC0gZmRhX2FwcHJvdmFsX2RhdGUNCiAgICAgICAgICAsIG9uX21hcmtldF9hZ2UgPSANCiAgICAgICAgICAgICAgYXBwbHkoLlNEWywgLih0ZXJtaW5hdGlvbl9kYXRlLmJvb2wsIHJlYWN0aXZhdGlvbl9kYXRlLmJvb2wsIHRlcm1pbmF0aW9uX2RhdGUpXQ0KICAgICAgICAgICAgICAgICAgICAsIDEsIGZ1bmN0aW9uKGkpeyBpZmVsc2UoaVtbMV1dLCBpZmVsc2UoaVtbMl1dLCB0b2RheSgpLCBpW1szXV0pLCB0b2RheSgpKSB9KSAtDQogICAgICAgICAgICAgIGFwcGx5KC5TRFssIC4odGVybWluYXRpb25fZGF0ZS5ib29sLCByZWFjdGl2YXRpb25fZGF0ZS5ib29sLCBtYXJrZXRfZGF0ZSwgcmVhY3RpdmF0aW9uX2RhdGUpXQ0KICAgICAgICAgICAgICAgICAgICAsIDEsIGZ1bmN0aW9uKGkpeyBpZmVsc2UoaVtbMV1dLCBpZmVsc2UoaVtbMl1dLCBpW1s0XV0sIGlbWzNdXSksIGlbWzNdXSkgfSkNCiAgICAgICAgICAsIGRheXNfbWFya2V0X2Fic2VudCA9IA0KICAgICAgICAgICAgICBhcHBseSguU0RbLCAuKHJlYWN0aXZhdGlvbl9kYXRlLmJvb2wsIHJlYWN0aXZhdGlvbl9kYXRlKV0NCiAgICAgICAgICAgICAgICAgICAgLCAxLCBmdW5jdGlvbihpKXsgaWZlbHNlKGlbWzFdXSwgaVtbMl1dLCB0b2RheSgpKSB9KSAtDQogICAgICAgICAgICAgICAgYXBwbHkoLlNEWywgLih0ZXJtaW5hdGlvbl9kYXRlLmJvb2wsIHRlcm1pbmF0aW9uX2RhdGUpXQ0KICAgICAgICAgICAgICAgICAgICAsIDEsIGZ1bmN0aW9uKGkpeyBpZmVsc2UoaVtbMV1dLCBpW1syXV0sIHRvZGF5KCkpIH0pDQogICAgICAgICAgLCB+ZGF5c190b19tYXJrZXQgKyBvbl9tYXJrZXRfYWdlICsgZGF5c19tYXJrZXRfYWJzZW50DQogICAgICAgICAgKQ0KICAgICAgKQ0KICAgICwgbW9kaWZ5X2F0KC5TRCwgYygidGVybWluYXRpb25fZGF0ZSIsICJyZWFjdGl2YXRpb25fZGF0ZSIpLCBcKHgpIGFzLkRhdGUoeCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKSkNCiAgKX0NCg0KIw0KKFwoeCwgaSwgYnkpew0KICBpIDwtIGRlZmluZSh4W2ksIG9uID0gYnksIGFsbG93LmNhcnRlc2lhbiA9IFRSVUVdKSA7DQogIGltYXAoLm5kY19ldmVudHNfbWV0YSwgXCh4LCB5KXsNCiAgICBybGFuZzo6aW5qZWN0KGRlc2NyKHggPSBtb2RpZnlfYXQoaSwgeSwgXChqKSBhcy5udW1lcmljKGosIHVuaXRzID0gImRheXMiKSksIHZhciA9ICEhcmxhbmc6OnN5bSh5KSwgdHJhbnNwb3NlID0gIVRSVUUpKSB8PiANCiAgICAgIHZpZXcobWV0aG9kID0gInJlbmRlciIsIHRhYmxlLmNsYXNzZXMgPSAnbXVsdGlfc3RhdCcsIGN1c3RvbS5jc3MgPSAibWFya2Rvd24uY3NzIikgfD4NCiAgICAgIHRhZ3MkdGQoKQ0KICB9KQ0KfSkobWFzdGVyX2RydWdfZGF0YSwgbmRjX2V2ZW50c19jbGVhbiwgYygiYWx0X25kYyIsICJmZGFfYXBwbGljYXRpb25fbnVtYmVyIiwgIm1hcmtldF9kYXRlIiwgInRlcm1pbmF0aW9uX2RhdGUiLCAicmVhY3RpdmF0aW9uX2RhdGUiLCAiZmRhX2FwcHJvdmFsX2RhdGUiKSkgfD4NCnRhZ3MkdHIoKSB8Pg0KdGFncyR0YWJsZSgpDQpgYGANCg0KU29tZSBvZiB0aGUgJ01heCcvJ01pbicgdmFsdWVzIGFyZSBuZWdhdGl2ZTsgaG93ZXZlciwgdGhlIG51bWJlciBvZiByZWNvcmRzIGlzIHJlbGF0aXZlbHkgc21hbGwgYW5kLCBtb3JlIGltcG9ydGFudGx5LCBleHBsYWluYWJsZToNCg0KLSBgZGF5c190b19tYXJrZXRgOiBBcHByb3ZhbCBvY2N1cnJlZCBhZnRlciB0aGUgbWFya2V0IGRhdGUNCi0gYGRheXNfbWFya2V0X2Fic2VudGA6IFJlY29yZHMgd2hlcmUgdGhlIHRlcm1pbmF0aW9uIGRhdGUgd2FzIG5vbi1gTkFgIGJ1dCBhZnRlciB0aGUgbWFya2V0IGRhdGUNCg0KIyMjIERydWcgRXZlbnRzPGJyPlZpc3VhbGl6YXRpb24NCg0KQ29tYmluaW5nIHRoZSBtYXN0ZXIgZHJ1ZyBkYXRhIGFuZCBldmVudCBkYXRhIChgbWFzdGVyX2RydWdfZGF0YWAgKyBgbmRjX2V2ZW50c19jbGVhbmApLCBhZnRlciBzb21lIHRyaWFsLWFuZC1lcnJvciwgSSBzZXR0bGVkIG9uIHRoZSBmb2xsb3dpbmcgc2hvd2luZyB0aGUgcm9vdC1tZWFuLXNxdWFyZSBvZiBtZXRyaWMgdmFsdWVzIGdyb3VwZWQgYnkgcm91dGUgb2YgYWRtaW5pc3RyYXRpb246DQoNCmBgYHtyIERSVUdfRVZFTlRTX1ZJWiwgZWNobz1GQUxTRX0gDQouY2FjaGUkc2V0KCJkcnVnX29ic19kYXRhIiwgew0KICBuZGNfZXZlbnRzX2NsZWFuWw0KICAgIG1hc3Rlcl9kcnVnX2RhdGENCiAgICAsIG9uID0gYygiYWx0X25kYyIsICJmZGFfYXBwbGljYXRpb25fbnVtYmVyIiwgImZkYV9hcHByb3ZhbF9kYXRlIiwgIm1hcmtldF9kYXRlIiwgInRlcm1pbmF0aW9uX2RhdGUiLCAicmVhY3RpdmF0aW9uX2RhdGUiKQ0KICAgICwgYWxsb3cuY2FydGVzaWFuID0gVFJVRQ0KICAgICwgbm9tYXRjaCA9IDANCiAgICBdWywgcm91dGVfc2l6ZSA6PSB1bmlxdWVOKG5kYyksIGJ5ID0gcm91dGVdIHw+IA0KICAgIHVuaXF1ZSgpIHw+DQogICAgbW9kaWZ5X2F0KGMoImRheXNfdG9fbWFya2V0IiwgIm9uX21hcmtldF9hZ2UiLCAiZGF5c19tYXJrZXRfYWJzZW50IiksIFwoeCkgYXMuZGlmZnRpbWUoeCwgdW5pdHMgPSAiZGF5cyIpKQ0KICB9KQ0KDQppZiAoISJkcnVnX29ic19kYXRhIiAlaW4lIGxzKCkpeyANCiAgbWFrZUFjdGl2ZUJpbmRpbmcoDQogICAgImRydWdfb2JzX2RhdGEiDQogICAgLCBmdW5jdGlvbigpIC5jYWNoZSRnZXQoImRydWdfb2JzX2RhdGEiKQ0KICAgICwgZW52ID0gZ2xvYmFsZW52KCkNCiAgICApDQp9DQoNCi5wbG90IDwtIHBsb3RfbHkod2lkdGggPSA5NjAsIGhlaWdodCA9IDcwMCkNCg0Kc3VwcHJlc3NXYXJuaW5ncyhkcnVnX29ic19kYXRhWywgLihyb3V0ZSwgZGF5c190b19tYXJrZXQsIG9uX21hcmtldF9hZ2UsIGRheXNfbWFya2V0X2Fic2VudCldIHw+IA0KICAjIGRwbHlyOjpzbGljZV9zYW1wbGUocHJvcCA9IDAuMSkgfD4NCiAgbWVsdChpZC52YXJzID0gInJvdXRlIiwgdmFyaWFibGUubmFtZSA9ICJNZXRyaWMiKSB8PiANCiAgc2V0b3JkZXIocm91dGUsIE1ldHJpYywgdmFsdWUpIHw+DQogIHNwbGl0KGJ5ID0gInJvdXRlIikgfD4NCiAgaXdhbGsoXCh4LCB5KXsgDQogICAgeCA8LSBkZWZpbmUoeCwgbWFwKC5TRCwgXChpKSBjYWxjLnJtcyhhcy5udW1lcmljKGkpIHw+IG1vZGlmeV9pZihpcy5uYSwgXChpKSAwKSkgfD4gYXMuZGlmZnRpbWUodW5pdHMgPSAiZGF5cyIpKSB+TWV0cmljKTsNCiAgICAucGxvdCA8PC0gYWRkX3RyYWNlKA0KICAgICAgICBwID0gLnBsb3QNCiAgICAgICAgLCB0eXBlID0gJ3NjYXR0ZXJwb2xhcicNCiAgICAgICAgLCBmaWxsID0gJ3Rvc2VsZicNCiAgICAgICAgLCByID0gaWZlbHNlKHgkdmFsdWUgPT0gMCwgMCwgbG9nKGFzLm51bWVyaWMoeCR2YWx1ZSksIGJhc2UgPSAzMCkpDQogICAgICAgICwgdGhldGEgPSB4JE1ldHJpYw0KICAgICAgICAsIGhvdmVyaW5mbyA9ICJ0ZXh0Ig0KICAgICAgICAsIGhvdmVydGV4dCA9IHNwcmludGYoDQogICAgICAgICAgICAgICI8Yj4lczwvYj48YnI+JS4yZiAlcyINCiAgICAgICAgICAgICAgLCB5DQogICAgICAgICAgICAgICwgaWZlbHNlKHgkdmFsdWUgPiAzNjUsIHgkdmFsdWUvMzY1LCB4JHZhbHVlKQ0KICAgICAgICAgICAgICAsIGlmZWxzZSh4JHZhbHVlID4gMzY1LCAiWWVhcnMiLCAiRGF5cyIpDQogICAgICAgICAgICAgICkNCiAgICAgICAgLCBuYW1lID0geQ0KICAgICAgICAsIG1vZGUgPSAibWFya2VycyINCiAgICAgICAgKSANCiAgfSkpDQoNCi5wbG90IHw+DQogIGNvbmZpZyhtYXRoamF4ID0gImNkbiIpIHw+DQogIGxheW91dCgNCiAgICB0aXRsZSA9IGxpc3QoDQogICAgICAgIHRleHQgPSBIVE1MKCJNZXRyaWNzIFJhZGFyIGJ5IFJvdXRlIG9mIEFkbWluaXN0cmF0aW9uIChMb2c8c3ViPjMwPC9zdWI+IERheXMpPGJyPjxzcGFuIHN0eWxlPSdmb250LXNpemU6c21hbGxlcjsgJz4oRG91YmxlLSBvciBzaW5nbGUtY2xpY2sgbGVnZW5kIGl0ZW1zKSIpDQogICAgICAgICwgbGVnZW5kID0gbGlzdChmb250ID0gbGlzdChzaXplID0gMTApKQ0KICAgICAgICAsIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJHZW9yZ2lhIikNCiAgICAgICAgKQ0KICAgICwgbWFyZ2luID0gbGlzdCh0ID0gLTAuNSkgDQogICAgKSB8Pg0KICB0YWdzJHAoKQ0KYGBgDQoNCiMjIFF1ZXN0aW9uIEk8YnI+PHN1cD5Db3JyZWxhdGlvbjwvc3VwPiB7LnRhYnNldH0NCg0KSSBkZWNpZGVkIHRvIHN0YXJ0IGFza2luZyBzb21lIHF1ZXN0aW9ucyBvZiB0aGUgZGF0YSBnaXZlbiB0aGUgbWV0cmljcyBkZWZpbmVkIGVhcmxpZXIuICBJIGRlY2lkZWQgdG8gbG9vayBpbnRvIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHBhaXJzIG9mIG1ldHJpY3MgcmVsYXRpdmUgdG8gYSB0aGlyZDoNCg0KIyMjIE1hcmtldCBBZ2UNCg0KIkhvdyBkb2VzIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuICpgZGF5c190b19tYXJrZXRgKiBhbmQgKmBvbl9tYXJrZXRfYWdlYCogY2hhbmdlIGJ5IHJvdXRlIG9mIGFkbWluaXN0cmF0aW9uPyI6DQoNCmBgYHtyIE1FVFJJQ1NfQ09SUkVMQVRJT05fSX0gDQpzdXBwcmVzc1dhcm5pbmdzKHN1cHByZXNzTWVzc2FnZXMoew0KbGlicmFyeShzbWFydC5kYXRhKQ0KDQppZiAoISJzbXJ0X2RydWdzIiAlaW4lIC5jYWNoZSRrZXlzKCkpew0KICBzbXJ0LmRydWdfb2JzX2RhdGEgPC0gc21hcnQuZGF0YSQNCiAgICBuZXcoeCA9IC5jYWNoZSRnZXQoImRydWdfb2JzX2RhdGEiKSB8PiBwcmludCgpLCBuYW1lID0gImRydWdzIikkDQogICAgdGF4b25vbXkucnVsZSgNCiAgICAgIHRlcm0ubWFwID0gaWYgKCJzbXJ0X2RydWdfdGF4b25vbXkiICVpbiUgLmNhY2hlJGtleXMoKSl7IC5jYWNoZSRnZXQoInNtcnRfZHJ1Z190YXhvbm9teSIpIH0gZWxzZSB7IGRhdGEudGFibGUodGVybSA9ICJtZXRyaWNzIiwgZGVzYyA9ICJNZXRyaWNzIHJlbGF0ZWQgdG8gZXZlbnRzIGFuZCBvdGhlciBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIikgfQ0KICAgICAgLCB1cGRhdGUgPSAhKCJzbXJ0X2RydWdfdGF4b25vbXkiICVpbiUgLmNhY2hlJGtleXMoKSkNCiAgICAgICkkDQogICAgY2FjaGVfbWdyKGFjdGlvbiA9IHVwZCkgfD4NCiAgICBpbnZpc2libGUoKTsNCiAgDQogIC5jYWNoZSRzZXQoInNtcnRfZHJ1Z3MiLCBzbXJ0LmRydWdfb2JzX2RhdGEpOw0KICAuY2FjaGUkc2V0KCJzbXJ0X2RydWdfdGF4b25vbXkiLCBzbXJ0LmRydWdfb2JzX2RhdGEkc21hcnQucnVsZXMkZm9yX3VzYWdlKQ0KfSBlbHNlIHsgDQogIGludmlzaWJsZShzbWFydC5kYXRhJG5ldyhhcy5kYXRhLnRhYmxlKHggPSAxKSwgIm5vbmUiKSkNCiAgLmNhY2hlJGdldCgic21ydF9kcnVncyIpJGNhY2hlX21ncihhY3Rpb24gPSB1cGQpIA0KfQ0KDQpnZXQuc21hcnQoImRydWdzIikkdXNlKGlkZW50aWZpZXIsIG1ldHJpY3MsIHJldGFpbiA9IGMocm91dGUpLCBzdWJzZXQgPSBkYXlzX3RvX21hcmtldCA+PSAwKSB8PiANCiAgIyBWaWV3KCkgDQogIHNldGtleShyb3V0ZSwgYWx0X25kYywgZGF5c190b19tYXJrZXQsIG9uX21hcmtldF9hZ2UpIHw+DQogIHNwbGl0X2YofnJvdXRlKSB8Pg0KICBtYXBfZGJsKFwoeCkgeCAlJCUgY29yKGFzLm51bWVyaWMoZGF5c190b19tYXJrZXQpLCBhcy5udW1lcmljKG9uX21hcmtldF9hZ2UpKSkgfD4NCiAgbW9kaWZ5X2lmKGlzLm5hLCBcKHgpIDApIHw+IA0KICAoXCh4KXsgDQogICAgeCA8LSB4W29yZGVyKHgpXQ0KICAgIG5tIDwtIG5hbWVzKHgpDQogICAgeiA8LSBjYWxjLnplcm9fbWVhbih4LCBhcy56c2NvcmUgPSBUUlVFLCB1c2UucG9wdWxhdGlvbiA9IFRSVUUpDQogICAgeSA8LSByYXRpbyh4ICsgYWJzKG1pbih4KSksIHR5cGU9InBhcmV0byIsIGRlY2ltYWxzID0gNikNCiAgICANCiAgICAud2hfc2NhbGUgPC0gODAwICogYygxLjIsIC43KQ0KICAgIA0KICAgIHBsb3RfbHkoDQogICAgICB4ID0geg0KICAgICAgLCB5ID0geQ0KICAgICAgLCBzaXplID0gNSAqIGV4cCh4KSArIDEwDQogICAgICAsIHdpZHRoID0gLndoX3NjYWxlWzFdDQogICAgICAsIGhlaWdodCA9IC53aF9zY2FsZVsyXQ0KICAgICAgLCBob3ZlcmluZm8gPSAidGV4dCINCiAgICAgICwgaG92ZXJ0ZXh0ID0gc3ByaW50ZihmbXQgPSI8Yj4lczwvYj48YnI+PGI+WTo8L2I+ICUuMmYlJSBvZiBUb3RhbDxicj48Yj5Db3I8L2I+KGRheXNfdG9fbWFya2V0LCBvbl9tYXJrZXRfYWdlKTogJS4yZjxicj48Yj5aLXNjb3JlPC9iPihYKTogJS4yZiIsIG5tLCB5ICogMTAwLCB4LCB6KQ0KICAgICAgLCBjb2xvciA9IHgNCiAgICAgICwgc3Ryb2tlID0gSSgiYmxhY2siKQ0KICAgICAgLCB0eXBlID0gInNjYXR0ZXIiLCBtb2RlID0gIm1hcmtlcnMiDQogICAgICApIHw+DQogICAgICBjb25maWcobWF0aGpheCA9ICJjZG4iLCBkaXNwbGF5TW9kZUJhciA9IEZBTFNFKSB8Pg0KICAgICAgbGF5b3V0KA0KICAgICAgICB4YXhpcyA9IGxpc3QoDQogICAgICAgICAgICB0aXRsZSA9IGxpc3QoDQogICAgICAgICAgICAgIHRleHQgPSAiWi1zY29yZTxzdWI+WDwvc3ViPjogWCB8IENvcihtPHN1Yj4wPC9zdWI+LCBtPHN1Yj4xPC9zdWI+KSB+IFJvdXRlIg0KICAgICAgICAgICAgICAsIGZvbnQgPSBsaXN0KHNpemUgPSAxNiwgZmFtaWx5ID0gIkdlb3JnaWEiKSkNCiAgICAgICAgICAgICwgZ3JpZGNvbG9yID0gIiNGRkZGRkYiDQogICAgICAgICAgICApDQogICAgICAgICwgeWF4aXMgPSBsaXN0KA0KICAgICAgICAgICAgdGl0bGUgPSBsaXN0KA0KICAgICAgICAgICAgICB0ZXh0ID0gIkN1bXVsYXRpdmUgUHJvcG9ydGlvbiAoWCkiDQogICAgICAgICAgICAgICwgZm9udCA9IGxpc3Qoc2l6ZSA9IDE2LCBmYW1pbHkgPSAiR2VvcmdpYSIpKQ0KICAgICAgICAgICAgLCBncmlkY29sb3IgPSAiI0ZGRkZGRiINCiAgICAgICAgICAgICkNCiAgICAgICAgLCB0aXRsZSA9IGxpc3QoDQogICAgICAgICAgICB0ZXh0ID0gc3ByaW50ZigiQ29ycmVsYXRpb24gQ29lZmZpY2llbnQgKDxzcGFuIHN0eWxlPSd0ZXh0LWVtcGhhc2lzLXBvc2l0aW9uOnVuZGVyOyB0ZXh0LWVtcGhhaXM6IGZpbGxlZCByZWQgZG91YmxlLWNpcmNsZTsgJz4lczwvc3Bhbj4gdnMuIDxzcGFuIHN0eWxlPSd0ZXh0LWVtcGhhc2lzLXBvc2l0aW9uOnVuZGVyOyB0ZXh0LWVtcGhhaXM6IGZpbGxlZCByZWQgZG91YmxlLWNpcmNsZTsgJz4lczwvc3Bhbj4pIGJ5IFJvdXRlIG9mIEFkbWluaXN0cmF0aW9uIiwgImRheXNfdG9fbWFya2V0IiwgIm9uX21hcmtldF9hZ2UiKQ0KICAgICAgICAgICAgLCBmb250ID0gbGlzdChmYW1pbHkgPSAiR2VvcmdpYSIpKQ0KICAgICAgICAsIHBsb3RfYmdjb2xvciA9IHJnYiguOCwuOCwuOCkNCiAgICAgICAgLCBtYXJnaW4gPSBsaXN0KGIgPSAzMCwgdCA9IDUwKQ0KICAgICAgICApIA0KICB9KSgpDQp9KSkNCmBgYA0KDQojIyMgRGF5cyBBYnNlbnQgZnJvbSBNYXJrZXQNCg0KKiJIb3cgZG9lcyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBgZGF5c190b19tYXJrZXRgIGFuZCBgZGF5c19tYXJrZXRfYWJzZW50YCBjaGFuZ2UgYnkgcm91dGUgb2YgYWRtaW5pc3RyYXRpb24/Iio6DQoNCmBgYHtyIE1FVFJJQ1NfQ09SUkVMQVRJT05fSUl9IA0KZ2V0LnNtYXJ0KCJkcnVncyIpJHVzZShpZGVudGlmaWVyLCBtZXRyaWNzLCByZXRhaW4gPSBjKHJvdXRlKSwgc3Vic2V0ID0gZGF5c190b19tYXJrZXQgPj0gMCkgfD4gDQogICMgVmlldygpIA0KICBzZXRrZXkocm91dGUsIGFsdF9uZGMsIGRheXNfbWFya2V0X2Fic2VudCwgb25fbWFya2V0X2FnZSkgfD4NCiAgc3BsaXRfZih+cm91dGUpIHw+DQogIG1hcF9kYmwoXCh4KSB4ICUkJSBzdXBwcmVzc1dhcm5pbmdzKGNvcihhcy5udW1lcmljKGRheXNfbWFya2V0X2Fic2VudCksIGFzLm51bWVyaWMob25fbWFya2V0X2FnZSkpKSkgfD4NCiAgbW9kaWZ5X2lmKGlzLm5hLCBcKHgpIDApIHw+IA0KICAoXCh4KXsgDQogICAgeCA8LSB4W29yZGVyKHgpXQ0KICAgIG5tIDwtIG5hbWVzKHgpDQogICAgeiA8LSBjYWxjLnplcm9fbWVhbih4LCBhcy56c2NvcmUgPSBUUlVFLCB1c2UucG9wdWxhdGlvbiA9IFRSVUUpDQogICAgeSA8LSByYXRpbyh4ICsgYWJzKG1pbih4KSksIHR5cGU9InBhcmV0byIsIGRlY2ltYWxzID0gNikNCiAgICANCiAgICAud2hfc2NhbGUgPC0gODAwICogYygxLjIsIC43KQ0KICAgIA0KICAgIHBsb3RfbHkoDQogICAgICB4ID0geg0KICAgICAgLCB5ID0geQ0KICAgICAgLCBzaXplID0gNSAqIGV4cCh4KSArIDEwDQogICAgICAsIHdpZHRoID0gLndoX3NjYWxlWzFdDQogICAgICAsIGhlaWdodCA9IC53aF9zY2FsZVsyXQ0KICAgICAgLCBob3ZlcmluZm8gPSAidGV4dCINCiAgICAgICwgaG92ZXJ0ZXh0ID0gc3ByaW50ZihmbXQgPSI8Yj4lczwvYj48YnI+PGI+WTo8L2I+ICUuMmYlJSBvZiBUb3RhbDxicj48Yj5Db3I8L2I+KGRheXNfdG9fbWFya2V0LCBkYXlzX21hcmtldF9hYnNlbnQpOiAlLjJmPGJyPjxiPlotc2NvcmU8L2I+KFgpOiAlLjJmIiwgbm0sIHkgKiAxMDAsIHgsIHopDQogICAgICAsIGNvbG9yID0geA0KICAgICAgLCBzdHJva2UgPSBJKCJibGFjayIpDQogICAgICAsIHR5cGUgPSAic2NhdHRlciINCiAgICAgICwgbW9kZSA9ICJtYXJrZXJzIg0KICAgICAgKSB8Pg0KICAgICAgY29uZmlnKG1hdGhqYXggPSAiY2RuIiwgZGlzcGxheU1vZGVCYXIgPSBGQUxTRSkgfD4NCiAgICAgIGxheW91dCgNCiAgICAgICAgeGF4aXMgPSBsaXN0KA0KICAgICAgICAgICAgdGl0bGUgPSBsaXN0KA0KICAgICAgICAgICAgICB0ZXh0ID0gIlotc2NvcmU8c3ViPlg8L3N1Yj46IFggfCBDb3IobTxzdWI+MDwvc3ViPiwgbTxzdWI+MTwvc3ViPikgfiBSb3V0ZSINCiAgICAgICAgICAgICAgLCBmb250ID0gbGlzdChzaXplID0gMTYsIGZhbWlseSA9ICJHZW9yZ2lhIikpDQogICAgICAgICAgICAsIGdyaWRjb2xvciA9ICIjRkZGRkZGIg0KICAgICAgICAgICAgKQ0KICAgICAgICAsIHlheGlzID0gbGlzdCgNCiAgICAgICAgICAgIHRpdGxlID0gbGlzdCgNCiAgICAgICAgICAgICAgdGV4dCA9ICJDdW11bGF0aXZlIFByb3BvcnRpb24gKFgpIg0KICAgICAgICAgICAgICAsIGZvbnQgPSBsaXN0KHNpemUgPSAxNiwgZmFtaWx5ID0gIkdlb3JnaWEiKSkNCiAgICAgICAgICAgICwgZ3JpZGNvbG9yID0gIiNGRkZGRkYiDQogICAgICAgICAgICApDQogICAgICAgICwgdGl0bGUgPSBsaXN0KA0KICAgICAgICAgICAgdGV4dCA9IHNwcmludGYoIkNvcnJlbGF0aW9uIENvZWZmaWNpZW50ICg8c3BhbiBzdHlsZT0ndGV4dC1lbXBoYXNpcy1wb3NpdGlvbjp1bmRlcjsgdGV4dC1lbXBoYWlzOiBmaWxsZWQgcmVkIGRvdWJsZS1jaXJjbGU7ICc+JXM8L3NwYW4+IHZzLiA8c3BhbiBzdHlsZT0ndGV4dC1lbXBoYXNpcy1wb3NpdGlvbjp1bmRlcjsgdGV4dC1lbXBoYWlzOiBmaWxsZWQgcmVkIGRvdWJsZS1jaXJjbGU7ICc+JXM8L3NwYW4+KSBieSBSb3V0ZSBvZiBBZG1pbmlzdHJhdGlvbiIsICJkYXlzX3RvX21hcmtldCIsICJkYXlzX21hcmtldF9hYnNlbnQiKQ0KICAgICAgICAgICAgLCBmb250ID0gbGlzdChmYW1pbHkgPSAiR2VvcmdpYSIpKQ0KICAgICAgICAsIHBsb3RfYmdjb2xvciA9IHJnYiguOCwuOCwuOCkNCiAgICAgICAgLCBtYXJnaW4gPSBsaXN0KGIgPSAzMCwgdCA9IDUwKQ0KICAgICAgICApIA0KfSkoKQ0KDQpgYGANCg0KPC9kaXY+DQpNYXJrZXQgYWdlIHJlbGF0aXZlIHRvIGRheXMgdG8gbWFya2V0IHNob3dzIG1vcmUgdmFyaWFiaWxpdHkgaW4gY29ycmVsYXRpb24gZGlzdHJpYnV0aW9uLiAgVGhpcyBpcyBub3QgdG8gbWFrZSBhbiBjbGFpbSBvZiBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGRpZmZlcmVudGlhdGlvbjsgaG93ZXZlciwgaXQgbWF5IGJlIHdvcnRoIGV4cGxvcmluZyB3aGV0aGVyIG9yIG5vdCB0aGVyZSBhcmUgY2x1c3RlcnMgb2YgYWRtaW5pc3RyYXRpb24gcm91dGVzIGJhc2VkIG9uIGV2ZW50IGNvcnJlbGF0aW9uLiAgQSBmdXR1cmUgdXBkYXRlIG1heSBhZGRyZXNzIHRoaXMsIGJ1dCB0aGlzIGlzIGFzIGRlZXAgb2YgZXhwbG9yYXRpb24gSSdsbCBnbyBmb3Igbm93Lg0KPGJyPg0KPGJyPg0KPGRpdj4NCg0KDQojIyBRdWVzdGlvbiBJSTxicj48c3VwPkV2ZW50IENsdXN0ZXJzPC9zdXA+PC9icj4NCg0KQW5vdGhlciB3YXkgdG8gbG9vayBhdCB0aGUgZGF0YSBpcyB0byBjb25zaWRlciBncm91cGVkIGR1cmF0aW9ucyBhbmQgdGhlIGNyb3NzLXRlbXBvcmFsIHNlcXVlbmNlcyBhbW9uZyB0aGVtLiAgQXMgdGhpcyBmb3JtcyB0aGUgYmFzaXMgb2YgYSBuZXR3b3JrLCBJJ2xsIHVzZSBjdXN0b20gcGFja2FnZSBbYGV2ZW50LnZlY3RvcnNgXShodHRwczovL2dpdGh1Yi5jb20vZGVscmlhYW4vZXZlbnQudmVjdG9ycykgdG8gYWNjb21wbGlzaCB0aGlzIGluIGEgZnV0dXJlIHVwZGF0ZS4=